'use strict'

let jsonpcallbackcounter = 0

let getJSON = (url, query, options = {}) => {

    const qs = _serialize(query)

    url += /\?/.test(url) ? '&' : '?';
    url += qs;

    return request(Object.assign({
        url,
        method: 'GET',
        responseType: 'json'
    }, options))
}

let post = (url, data, options = {}) => {

    return request(Object.assign({
        url,
        method: 'POST',
        body: _serialize(data),
        responseType: 'json',
        headers: {
            'Content-type': 'application/x-www-form-urlencoded'
        }
    }, options))

}

function request(options) {

    let defaults = {
        url: '',
        method: 'GET',
        body: null,
        headers: null,
        async: true,
        responseType: '',
        timeout: 7 * 1000,
        cors: false,
        onSuccess: _noop,        // common business logic before calling promise fullfill
        onError: _noop           // common error handling before calling promise reject
    }

    options = Object.assign({}, defaults, options)

    return new Promise((fullfill, reject) => {
        const xhr = new XMLHttpRequest();
        let headers = options.headers;

        try {
            xhr.responseType = options.responseType
            xhr.timeout = options.timeout
        } catch (e) {
            // catch error for legacy device versions
        }

        xhr.open(options.method, options.url, options.async)

        try {
            options.cors && (xhr.withCredentials = true)
        } catch (e) {
            // catch error for legacy device versions
        }

        if (headers) {
            for (let h in options.headers) {
                xhr.setRequestHeader(h, headers[h])
            }
        }

        xhr.onreadystatechange = () => {

            if (xhr.readyState == XMLHttpRequest.DONE) {
                if (xhr.status >= 200 && xhr.status < 300) {

                    let response
                    if (typeof xhr.response === 'string' && options.responseType === 'json') {
                        // compatible for Android <= 4.4 and iOS <= 7.1, as XHR2 is not supported
                        response = JSON.parse(xhr.response)
                    } else {
                        response = xhr.response
                    }

                    if (options.onSuccess) {

                        if (!options.onSuccess(response)) {
                            return
                        }

                    }

                    fullfill(response)

                } else {

                    if (options.onError) {
                        if (!options.onError(xhr.status)) {
                            return
                        }
                    }

                    reject(xhr.status)
                }
            }

        }

        xhr.ontimeout = () => {
            options.onError && options.onError(xhr.status)
            reject('timeout')
        }

        xhr.send(options.body)
    })
}

let jsonp = (url, query, callbackkey) => {
    // default callbackkey
    callbackkey = callbackkey || 'callback'

    // unique callbackname for each call, to avoid conflicts and override
    const callbackname = 'jsonpcallback' + jsonpcallbackcounter++
    const script = document.createElement('script')
    query[callbackkey] = callbackname
    const qs = _serialize(query)
    url += /\?/.test(url) ? '&' : '?'
    url += qs
    script.src = url
    return new Promise((fullfill, reject) => {
        document.body.appendChild(script)
        window[callbackname] = res => {
            fullfill(res)
        }
        script.onerror = () => {
            reject()
        }
    })
}

function _noop() {/** no operation **/}

function _serialize(data) {
    let qsArr = []
    for (let key in data) {
        let val = data[key]
        qsArr.push(encodeURIComponent(key) + '=' + encodeURIComponent(typeof val == 'object' ? JSON.stringify(val) : val))
    }
    return qsArr.join('&')
}

export default {
    getJSON,
    post,
    request,
    jsonp
}